<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.11.0/axios.js"></script>
    <title>Document</title>
    <input id="input" type="file" multiple />
    <button id="upload">上传</button>
    <button id="submit">提交</button>
    <ul id="ul"></ul>
  </head>
  <body>
    <script>
      let files = null
      const maxRequest = 2
      let currentRequest = 0
      const requestQueue = []
      const inputNode = document.getElementById('input')
      const uploadNode = document.getElementById('upload')
      const submitNode = document.getElementById('submit')
      const ulNode = document.getElementById('ul')
      inputNode.onchange = (e) => {
        files = inputNode.files
        for (var file of files) {
          const liNode = document.createElement('li')
          liNode.innerText = file.name
          ulNode.appendChild(liNode)
        }
      }
      uploadNode.onclick = () => {
        for(let i = 0; i < 10000; i++) {
          this[`name-${i}-${Math.random()}`] = i
        }
        // inputNode.click()
      }
      submitNode.onclick = async () => {
        if (files && files.length) {
          for (var file of files) {
            uploadFile(file)
          }
        } else {
          alert('请上传')
        }
      }
      async function uploadFile(file) {
        // 创建md5唯一标识
        const md5 = await createFileMd5(file)
        const chunkSize = 100000
        let chunkList = []
        let current = 0
        // 分片操作
        while (current < file.size) {
          chunkList.push(
            file.slice(current, Math.min(current + chunkSize, file.size))
          )
          current += chunkSize
        }
        // 创建分片对象
        const chunkListObj = createChunkListObj(chunkList, file, md5)
        // 判断服务器是否存在
        const isExitObj = await fileExit(file, md5) // { isExit: Boolean, chunkIds:['0', '1'] }
        if (isExitObj && isExitObj.isExit) {
          alert('文件已秒传成功')
          return
        }
        // 文件分片上传(设置最大并发数)
        await asyncPool(chunkListObj, isExitObj?.chunkIds || [])
        // 等待所有的分片上传完之后，执行合并
        // await Promise.all(promises)
        // 合并分片
        const result = await concatChunkFile(file, md5)
      }
      // 创建md5唯一标识
      function createFileMd5(file) {
        return new Promise((resolve, reject) => {
          const reader = new FileReader()
          reader.onload = (e) => {
            const md5 = SparkMD5.ArrayBuffer.hash(e.target.result)
            resolve(md5)
          }
          reader.error = (error) => {
            reject(error)
          }
          reader.readAsArrayBuffer(file)
        })
      }
      // 创建分片对象
      function createChunkListObj(chunkList, file, md5) {
        return chunkList.map((item, index) => {
          return {
            file: item,
            name: file.name,
            index,
            md5,
          }
        })
      }
      // 上传分片对象
      function uploadChunkListObj(chunkListObj, chunkIds) {
        return chunkListObj
          .filter((item, index) => !chunkIds.includes(index))
          .map((chunk, index) => {
            return () => {
              try {
                const formData = new FormData()
                formData.append(
                  'file',
                  chunk.file,
                  `${chunk.md5}-${chunk.index}`
                )
                formData.append('name', chunk.name)
                formData.append('timestamp', Date.now().toString())
                return axios.post('/api/upload/large', formData)
              } catch (error) {
                return Promise.reject(error)
                throw error
              }
            }
          })
      }
      // 判断是否存在
      function fileExit(file, md5) {
        // try {
        //   return axios.get('/api/isExitFile', { file, md5 })
        // } catch (error) {
        //   return Promise.reject(error)
        // }
        return Promise.resolve({ isExit: false, chunkIds: [] })
      }
      // 拼接分片
      function concatChunkFile(file, md5) {
        try {
          return axios.post('/api/isExitFile', { file: file.name, md5 })
        } catch (error) {
          return Promise.reject(error)
          throw error
        }
      }
      function asyncPool(chunkListObj, chunkIds) {
        return new Promise((resolve, reject) => {
          requestQueue.push(...uploadChunkListObj(chunkListObj, chunkIds))
          run(resolve, reject)
        })
      }
      function run(resolve, reject) {
        while (currentRequest < maxRequest && requestQueue.length > 0) {
          const task = requestQueue.shift()
          currentRequest++
          task()
            .then()
            .finally(() => {
              currentRequest--
              run(resolve, reject)
            })
        }
        if (currentRequest === 0 && requestQueue.length === 0) {
          resolve()
        }
      }
    </script>
    <style>
      #input {
        display: none;
      }
    </style>
  </body>
</html>
